package game;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import util.DebugOut;

import game.model.Card;
import glassfrog.model.Gamedef;

/**
 * Contains all information about the current state of the game
 * with all necessary information available for the player.
 * 
 * @author Witthold/Korol
 */
public class State {

	private Gamedef gamedef;
	private int currentHand;
	private Player[] players;
	private int button;
	private int currentPlayer;
	private Round[] rounds;
	private boolean buttonClockwise = true;
	//	private boolean buttonClockwise = false;
	private int currentRound;


	//	private int[] winsPerPlayer;

	public State(Gamedef gamedef) {	// TODO set buttonClockwise in this constructor depending if it is Talk_ACPC

		this.gamedef = gamedef;

		initBoardCards();
	}


	/**
	 * Prepares the storage of the board cards.
	 */
	private void initBoardCards() {

		int[] numPubCards = gamedef.getNumPublicCards();
		rounds = new Round[numPubCards.length];

		//		for (int i = 0; i < rounds.length; i++) {
		//
		//			rounds[i] = new Round(numPubCards[i]);
		//		}
	}


	/**
	 * Stores names & buyins of the players.
	 * 
	 * @param names all names of the players
	 * @param buyins buy ins of the players
	 */
	public void setPlayers(String[] names, int[] buyins) {

		players = new Player[names.length];
		//		winsPerPlayer = new int[players.length];

		for (int i = 0; i < names.length; i++) {

			if (isDoylesGame()) {

				players[i] = new DoylesPlayer(names[i], gamedef.getStackSize());

			} else {

				players[i] = new Player(names[i], buyins[i]);
			}
		}

		button = players.length - 1;
		//button = 2 % players.length; // TODO verify correctness in heads up games

	}


	public void updatePlayerPositions() {

		int nextPosition = 0;

		for (int i = 0; i < players.length; i++) {

			Player player = players[(i + button + 1) % players.length];

			if (player.isActive()) {

				player.setCurrentPosition(nextPosition);
				nextPosition++;

			} else {

				player.setCurrentPosition(-1);
			}
		}
	}


	private Action generateAction(int currentRound, char actionChar, int player) {

		int change;

		if (actionChar == 'c') {

			change = getHighestBet() - players[player].getCurrentBet();

		} else if (actionChar == 'r') {

			change = getHighestBet() - players[player].getCurrentBet() + gamedef.getBet(currentRound);

		} else {

			change = 0;
		}

		return new Action(getPotSize(), player, actionChar, change);
	}


	public int getHighestBet() {

		int highest = 0;

		for (Player player : players) {

			if (player.getCurrentBet() > highest) {

				highest = player.getCurrentBet();
			}
		}

		return highest;
	}

	
	public int getPotSize() {

		int potsize = 0;

		for (Player player : players) {

			potsize += player.getCurrentBet();
		}

		return potsize;
	}

	
	public int[][] setGetBlinds() {	// TODO handle growing of blind sizes

		int sb = gamedef.getSmallBlind();
		int[] blinds = getBlindPoster();
		int[] sizes = gamedef.getBlindStructure();
		int[][] result = new int[blinds.length][2];
		
		for( int i = 0; i < blinds.length; i++) {
			
			int size = sizes[i] * sb;
			
			setBlind(blinds[i], size);
			
			result[i][0] = blinds[i];
			result[i][1] = size;
		}
		
		currentPlayer = getNextActivePlayer(blinds[blinds.length -1]);	// MAYBE this only works for gamedefs without ante in blindStructure
		
		return result;
	}


	private void setBlind(int player, int size) {

		Action action = new Action(getPotSize(), player, 'b', size);
		rounds[0].add(action);
		players[player].applyAction(action);

		if (players[currentPlayer].isAllIn()) {

			action.setAllIn();
		}

		DebugOut.showVerboseGame("Blind: P" + action.getPlayer() + " " + action.getChange());
	}


	public void setNextActivePlayer() {

		currentPlayer = getNextActivePlayer();
	}


	public int getNextActivePlayer() {

		return getNextActivePlayer(currentPlayer);
	}


	public int getNextActivePlayer(int position) {

		for (int i = 1; i <= players.length; i++) {

			int nextPlayer = (position + i) % players.length;

			if (players[nextPlayer].isActive()) {

				return nextPlayer;
			}
		}
		return -1;
	}


	private int getPreviousActivePlayer(int position) {	// TODO move this functionality into a method "moveButton(bool clockwise)"?

		for (int i = 1; i <= players.length; i++) {

			int previousPlayer = (players.length + position - i) % players.length;

			if (players[previousPlayer].isActive()) {

				return previousPlayer;
			}
		}
		return -1;
	}


	public void setHoleCards(int player, Card[] cards) {

		players[player].setHoleCards(cards);
	}


	public void setHoleCards(Card[][] holeCards) {

		for (int i = 0; i < holeCards.length; i++) {

			int position = players[i].getCurrentPosition();

			if (position < holeCards.length && position != -1) {
				if (holeCards[position] != null) {

					players[i].setHoleCards(holeCards[position]);
				}
			}
		}
	}


	/**
	 * Stores the revealed board cards of the given round.
	 * 
	 * @param round
	 * 			Round in which given cards are revealed
	 * @param cards
	 * 			Revealed cards
	 */			
	public void setBoardCards(int round, Card[] cards) {

		rounds[round] = new Round(cards);
	}


	/**
	 * Returns the players as an array.
	 * 
	 * @return
	 * 			Player array
	 */
	public Player[] getPlayers() {

		return players;
	}


	/**
	 * Returns all remaining active players.
	 * 
	 * @return
	 * 			Array of active players
	 */
	public Player[] getActivePlayers() {

		ArrayList<Player> actives = new ArrayList<Player>();

		for (Player player : players) {

			if (player.isActive()) {

				actives.add(player);
			}
		}

		//return (Player[]) actives.toArray();	// this line throws a can't cast exception
		Player[] temp = new Player[actives.size()];
		return actives.toArray(temp);
	}


	/**
	 * Returns the bet history of the given round as a string.
	 * 
	 * @param round
	 * 			Round of the bet history 
	 * @return
	 * 		 	The bet history of a round as a string
	 */
	public String getHistory(int round) {
		return rounds[round].gameStorageToString();
	}


	/**
	 * Returns if the bot has the option to check.
	 * 
	 * @return
	 * 			true if the bot has the option to check
	 */
	public boolean isCheckable() {

		if (getHighestBet() == players[0].getCurrentBet()) {

			return true;

		} else {

			return false;
		}
	}


	/**
	 * Checks if it is the bot's turn.
	 * 
	 * @return
	 * 			true if the bot is the current player 
	 */
	public boolean isMyMove() {

		return currentPlayer == 0;
	}


	/**
	 * Sets the current player to the given index.
	 * 
	 * @param player
	 * 			Index of the who should be the current player
	 */
	public void setCurrentPlayer(int player) {

		currentPlayer = player;
	}


	/**
	 * Sets the hand counter to the given and updates the position of the players.</br>
	 * If it is the first hand, it also arranges the players so that the own index is 0.
	 * 
	 * @param ownPosition
	 *            The own position in this hand
	 * @param hand
	 *            The number hand of the current hand.
	 */
	public void setHand(int ownPosition, int hand) {

		currentHand = hand;

		if (hand == 0) {

			reorderPlayers(ownPosition);

			for (Player player : players) {

				player.setCashAtStartOfHand(player.getCurrentCash());
			}

		} else {

			reset();
			newHand();
		}

		updatePlayerPositions();
	}


	/**
	 * Changes the players array to an order in which the own index is 0, the player next to one self 1 and so on.
	 * 
	 * @param ownIndex 
	 * 			Index of the own player before the ordering 
	 */
	public void reorderPlayers(int ownIndex) {

		Player[] result = new Player[players.length];

		for (int i = 0; i < players.length; i++) {

			result[i] = players[(ownIndex + i) % players.length];
			result[i].setInitPosition((ownIndex + i) % players.length);
		}

		players = result;
		button = players.length - 1 - ownIndex;
		currentPlayer = (button + 3) % players.length;
	}


	/**
	 * Resets the player states and board cards for the beginning of a new hand.
	 */
	public void reset() { // TODO if it is doylesgame: reset cash to stack size?

		for (Player player : players) {

			player.reset();
		}

		initBoardCards();
	}


	public void printState() {

		DebugOut.showVerboseState("#######");

		// button
		DebugOut.showVerboseState("#######");
		DebugOut.showVerboseState("Button: " + button);

		// match
		DebugOut.showVerboseState("#######");
		DebugOut.showVerboseState("Hand: " + currentHand);

		//pot size
		DebugOut.showVerboseState("#######");
		DebugOut.showVerboseState("Pot size: " + getPotSize());
		
		// board cards
		DebugOut.showVerboseState("#######");
		DebugOut.showVerboseState("Board card: ");
		for (Card card : getBoardCards()) {
					
			DebugOut.showVerboseState(card.toString() + " ");
		}		
		DebugOut.showVerboseState("");
						
						
		// players with name, cash, hand, bet gameStorage
		for (Player player : players) {

			DebugOut.showVerboseState("#######");
			// DebugOut boolean player, not state!
			player.printPlayerData();

		}

		DebugOut.showVerboseState("#######");

		for (int i = 0; i < rounds.length; i++) {

			if (rounds[i] != null) {
				DebugOut.showVerboseState("Round " + i + ":");
				DebugOut.showVerboseState("created: " + rounds[i].getTimestamp());
				//			DebugOut.showVerboseState("Board cards: " + rounds[i].boardCardsToString());
				//			DebugOut.showVerboseState("History: " + rounds[i].gameStorageToString());
				DebugOut.showVerboseState(rounds[i].toString());
			}

		}

		DebugOut.showVerboseState("#######");

		int[] rankings = getRankings();

		DebugOut.showVerboseState("Rankings:");
		for (int i = 0; i < rankings.length; i++) {
			DebugOut.showVerboseState(i + ": " + rankings[i]);
		}
		DebugOut.showVerboseState("#######");

	}


	public void showdown(int[] winsPerPlayer) {

		for (int i = 0; i < winsPerPlayer.length; i++) {
					
			if (isDoylesGame()) {

				//players[i].applyDelta(playerWon - players[i].getBet());
				((DoylesPlayer) players[i]).applyDelta(winsPerPlayer[i] - players[i].getCurrentBet());

			}

			//players[i].addCash(playerWon);
			players[i].addCash(winsPerPlayer[i]);

		}
	}


	/**
	 * Checks if the game is a "Doyle's game".</br>
	 * It is only possible for no limit games!
	 */
	private boolean isDoylesGame() {
		return gamedef.isDoylesGame() && gamedef.isNoLimit();
	}


	/**
	 * Returns an integer representing the game style.
	 * 
	 * @return 1: cash game</br>
	 *         2: tournament</br>
	 *         3: doyle's game
	 */
	public int getStyle() {

		if (isDoylesGame()) {

			return 3;

		} else if (isTournament()) {

			return 2;

		} else {

			return 1;
		}
	}


	private boolean isTournament() {

		// TODO implement tournament style		 
		return false;
	}


	public int getLimit() {

		// TODO implement style calculation
		if (gamedef.isNoLimit()) {

			return -1;

			//		} else if (false) {	// TODO implement pot limit
			//			
			//			return 0;
			//			
		} else {
			return 1;

		}
	}


	public int[] getPositions() {

		int[] result = new int[players.length];

		for (int i = 0; i < players.length; i++) {

			result[i] = players[i].getCurrentPosition();
		}

		return result;
	}


	public Action addAction(int round, char actionChar) {

		Action action = generateAction(round, actionChar, currentPlayer);

		rounds[round].add(action);
		players[currentPlayer].applyAction(action);

		if (players[currentPlayer].isAllIn()) {

			action.setAllIn();
		}

		currentPlayer = getNextActivePlayer();

		return action;
	}


	public Action addAction(int round, char actionChar, int totalInPot) { // TODO use a method addAction(Action) for all addAction methods

		int change = totalInPot - players[currentPlayer].getCurrentBet();

		Action action = new Action(getPotSize(), currentPlayer, actionChar, change);

		rounds[round].add(action);
		players[currentPlayer].applyAction(action);

		if (players[currentPlayer].isAllIn()) {

			action.setAllIn();
		}

		currentPlayer = getNextActivePlayer();

		return action;
	}


	public void newRound() {

		currentPlayer = getNextActivePlayer(button);
//		getNextActivePlayer(); // next active player after button begins the new round

		currentRound++;
	}


	public void newHand() {

		//		currentPlayer = button;
		//		setNextActivePlayer();
		//		button = currentPlayer;

		if (buttonClockwise) {

			button = getNextActivePlayer(button);

		} else {

			button = getPreviousActivePlayer(button);
		}

		currentPlayer = button;
		currentRound = 0;

		for (Player player : players) {

			player.setCashAtStartOfHand(player.getCurrentCash());
		}
	}


	public int getMinBet() {

		return gamedef.getMinBet();
	}


	public int getMaxBet() {

		return gamedef.getMaxBet();
	}


	public int getBuyin() {

		if (isDoylesGame()) {

			DebugOut.showVerboseState("doyles game, stack: " + gamedef.getStackSize());
			return gamedef.getStackSize();

		} else {

			DebugOut.showVerboseState("not doyles game! (No minimal buy in available)");
			return 0;
		}
	}


	public int getSmallBlind() {

		return gamedef.getBlind(0); // TODO	implement growing of blinds
	}


	public int getBigBlind() {

		return gamedef.getBlind(1); // TODO	implement growing of blinds
	}


	public int getAnte() {

		return 0; // TODO implement ante in State
	}


	public Round getRound() {

		return rounds[currentRound];
	}


	public Round getRound(int n) {

		if (n <= currentRound) {

			return rounds[n];

		} else {

			return null;
		}
	}


	public int getCurrentRound() {
		return currentRound;
	}


	public int[] getCashDeltas() {

		int[] result = new int[players.length];

		for (int i = 0; i < result.length; i++) {

			result[i] = players[i].getCurrentCash() - players[i].getCashAtStartOfHand();
		}

		return result;
	}


	public int[] getRankings() {

		int[] rankings = new int[players.length];

		//		DebugOut.showVerboseState("before: ");
		//		for (Player player : players) {
		//			
		//			player.printPlayerData();
		//			DebugOut.showVerboseState();
		//		}

		Player[] sorted = players.clone();

		Arrays.sort(sorted, Collections.reverseOrder());

		//		DebugOut.showVerboseState("sorted: ");
		//		for (Player player : sorted) {
		//			
		//			player.printPlayerData();
		//			DebugOut.showVerboseState();
		//		}

		for (int i = 0; i < players.length; i++) {

			if (sorted[0].getName() == players[i].getName()) {

				rankings[i] = 0;
			}
		}

		int rank = 1;

		for (int i = 1; i < sorted.length; i++) {

			for (int j = 0; j < players.length; j++) {

				if (sorted[i].getName().equals(players[j].getName())) {

					if (sorted[i - 1].compareTo(sorted[i]) > 0) {

						rankings[j] = rank;

					} else {

						rankings[j] = rankings[j - 1];
					}
					rank++;
					break;
				}
			}
		}

		return rankings;
	}


	public int[] getBetStructure() {

		return gamedef.getBetStructure();

	}


	public int[] getBlindStructure() {

		return gamedef.getBlindStructure();
	}


	public int getButton() {
		return button;
	}


	public int[] getBlindPoster() {

		int nextBlind;
		int[] result = new int[gamedef.getBlindStructure().length];

		if (players.length == 2 && gamedef.isReverseBlinds()) {

			nextBlind = button;

		} else {

			nextBlind = getNextActivePlayer(button);
		}

		for (int i = 0; i < result.length; i++) {

			result[i] = nextBlind;
			nextBlind = getNextActivePlayer(nextBlind);
		}

		return result;
	}
	
	
	public int getGamedefsMinPlayers() {
		return gamedef.getMinPlayers();
	}


	public Card[] getBoardCards() {

		ArrayList<Card> cards = new ArrayList<Card>();

		for (Round round : rounds) {

			if(round != null && round.boardCardsLength() != 0) {
				
				for (Card card : round.getBoardCards()) {

					cards.add(card);
				}
			}
			
		}
		//return (Player[]) actives.toArray();	// this line throws a can't cast exception
		Card[] temp = new Card[cards.size()];
		return cards.toArray(temp);
	}


	public Card[] getDeadCards() {
		// TODO implement getDeadCards in State
		return null;
	}


	public int getPlayerByName(String string) {

		for (int i = 0; i < players.length; i++) {
			
			if(players[i].getName().equals(string)) {
				return i;
			}
		}
		
		return -1;
	}
}
